﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Security;
using System.Security.Permissions;

//using Xceed.Wpf.DataGrid.Markup;
//using Xceed.Utils;
//using Xceed.Utils.Math;

namespace Xceed.Silverlight.Data.Stats
{
  public abstract class StatFunction
  {
    static StatFunction()
    {
#if DEBUG
      //StatFunction.TestStatFunctions();
#endif
    }

    protected StatFunction()
    {

    }

    protected StatFunction( string resultPropertyName, string sourcePropertyName )
      : this()
    {
      m_resultPropertyName = resultPropertyName;
      m_sourcePropertyName = sourcePropertyName;
      this.ExtractSourcePropertyNames();
    }

    #region ResultPropertyName Property

    public string ResultPropertyName
    {
      get
      {
        return m_resultPropertyName;
      }
      set
      {
        this.CheckSealed();
        m_resultPropertyName = value;
      }
    }

    private string m_resultPropertyName;

    #endregion

    #region SourcePropertyName Property

    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly" )]
    public string SourcePropertyName
    {
      get
      {
        return m_sourcePropertyName;
      }
      set
      {
        if( value == null )
          throw new ArgumentNullException( "SourcePropertyName" );

        if( value.Trim().Length == 0 )
          throw new ArgumentException( "SourcePropertyName cannot be empty.", "SourcePropertyName" );

        this.CheckSealed();
        m_sourcePropertyName = value;
        this.ExtractSourcePropertyNames();
      }
    }

    private string m_sourcePropertyName;

    #endregion

    #region PrerequisiteFunctions Property

    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays" )]
    protected internal virtual StatFunction[] PrerequisiteFunctions
    {
      get
      {
        return null;
      }
    }

    #endregion

    #region IsSealed Property

    protected bool IsSealed
    {
      get
      {
        return m_sealed;
      }
    }

    private bool m_sealed = false;

    #endregion

    #region InCalculation Property

    internal bool InCalculation
    {
      get
      {
        return m_inCalculation;
      }
    }

    #endregion

    #region RequiresAccumulation Property

    protected internal virtual bool RequiresAccumulation
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region IsInitialized Property

    protected internal virtual bool IsInitialized
    {
      get
      {
        return m_initialized;
      }
    }

    #endregion

    public IList<string> GetSourcePropertyNames()
    {
      if( m_readOnlySourcePropertyNames == null )
      {
        m_readOnlySourcePropertyNames = new ReadOnlyCollection<string>( m_sourcePropertyNames );
      }

      return m_readOnlySourcePropertyNames;
    }

    protected internal virtual void Validate()
    {
      if( ( m_resultPropertyName == null ) || ( m_resultPropertyName.Length == 0 ) )
        throw new InvalidOperationException( "The ResultPropertyName property must be set." );
    }

    protected internal virtual void InitializeFromInstance( StatFunction source )
    {
      this.SourcePropertyName = source.SourcePropertyName;
    }

    protected virtual void Initialize( Type[] sourcePropertyTypes )
    {
    }

    protected internal abstract void Reset();

    protected internal virtual void InitializePrerequisites( StatResult[] prerequisitesValues )
    {
      throw new NotImplementedException( "InitializePrerequisites must also be overridden when PrerequisiteFunctions has been overridden." );
    }

    protected internal abstract void Accumulate( object[] values );

    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate" )]
    protected internal abstract StatResult GetResult();

    protected void CheckSealed()
    {
      if( m_sealed )
        throw new InvalidOperationException( "An attempt was made to change a statistical function that has already been added to the StatFunctions collection." );
    }

    protected internal virtual bool IsEquivalent( StatFunction statFunction )
    {
      if( statFunction == null )
        return false;

      return ( this.GetType() == statFunction.GetType() ) &&
             ( this.SourcePropertyName == statFunction.SourcePropertyName );
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate" )]
    protected internal virtual int GetEquivalenceKey()
    {
      return this.GetType().GetHashCode() ^ this.SourcePropertyName.GetHashCode();
    }

    internal void InitializeAccumulationTypes( Type[] sourcePropertyTypes )
    {
      this.Initialize( sourcePropertyTypes );
      m_initialized = true;
    }

    internal void StartCalculation()
    {
      m_inCalculation = true;
    }

    internal void EndCalculation()
    {
      m_inCalculation = false;
    }

    internal static bool AreEquivalents( StatFunction statFunctionA, StatFunction statFunctionB )
    {
      if( ( statFunctionA != null ) && ( statFunctionB != null ) )
      {
        return statFunctionA.IsEquivalent( statFunctionB );
      }
      else
      {
        return ( statFunctionA == null ) && ( statFunctionB == null );
      }
    }

    internal void ValidateSourcePropertyName( int expectedPropertyCount )
    {
      if( this.GetSourcePropertyNames().Count != expectedPropertyCount )
        throw new InvalidOperationException( string.Format( "The SourcePropertyName property must be set to {0} field{1} with the Statistical function {2}.",
          expectedPropertyCount, ( expectedPropertyCount > 1 ) ? "s" : "", this.GetType().Name ) );
    }

    internal void Seal()
    {
      m_sealed = true;
    }

    internal static bool CanProcessValues( object[] values, int count )
    {
      if( count > values.Length )
        return false;

      for( int i = 0; i < count; i++ )
      {
        if( ( values[ i ] == null ) || ( values[ i ] is DBNull ) )
          return false;
      }

      return true;
    }

    internal static TypeCode GetDefaultNumericalAccumulationType( Type dataType )
    {
      TypeCode accType = TypeCode.Empty;

      switch( Type.GetTypeCode( dataType ) )
      {
        case TypeCode.Char:
        case TypeCode.Byte:
        case TypeCode.SByte:
        case TypeCode.Int16:
        case TypeCode.Int32:
        case TypeCode.Int64:
        case TypeCode.UInt16:
        case TypeCode.UInt32:
        case TypeCode.UInt64:
          accType = TypeCode.Int64;
          break;

        case TypeCode.Single:
        case TypeCode.Double:
          accType = TypeCode.Double;
          break;

        case TypeCode.Decimal:
          accType = TypeCode.Decimal;
          break;
      }

      return accType;
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly" )]
    private void ExtractSourcePropertyNames()
    {
      if( m_sourcePropertyName != null )
      {
        m_sourcePropertyNames.Clear();
        m_sourcePropertyNames.AddRange( m_sourcePropertyName.Split( ',' ) );

        for( int i = 0; i < m_sourcePropertyNames.Count; i++ )
        {
          m_sourcePropertyNames[ i ] = m_sourcePropertyNames[ i ].Trim();

          if( m_sourcePropertyNames[ i ].Length == 0 )
            throw new ArgumentException( "A multi-part SourcePropertyName cannot include an empty property name.", "SourcePropertyName" );
        }
      }
    }

#if DEBUG
    //public static void TestStatFunctions()
    //{
    //  Debug.WriteLine( "STARTING THE STATFUNCTIONS SELF-TESTS. Ignore the following exception system traces." );

    //  StatResult result;
    //  Type[] accTypeString = new Type[] { typeof( string ) };
    //  Type[] accTypeInt16 = new Type[] { typeof( short ) };
    //  Type[] accTypeInt32 = new Type[] { typeof( int ) };
    //  Type[] accTypeDouble = new Type[] { typeof( double ) };
    //  Type[] accTypeDecimal = new Type[] { typeof( decimal ) };
    //  Type[] accTypeInt32X2 = new Type[] { typeof( int ), typeof( int ) };
    //  Type[] accTypeDoubleX2 = new Type[] { typeof( double ), typeof( double ) };
    //  Type[] accTypeDecimalX2 = new Type[] { typeof( decimal ), typeof( decimal ) };


    //  // ***************
    //  // MaximumFunction
    //  // ***************
    //  MaximumFunction max = new MaximumFunction();
    //  result = RunStatFunction( max, accTypeDoubleX2, 1, 10, 32, 150, -45 );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == 150 );

    //  result = RunStatFunction( max, accTypeDoubleX2, 1, 10, 32, 150, -45, null, DBNull.Value );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == 150 );

    //  result = RunStatFunction( max, accTypeDoubleX2, 1f, 10f, 32f, 150.5f, -45f, null, DBNull.Value );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == 150.5 );

    //  result = RunStatFunction( max, accTypeDoubleX2, 1m, 10.6m, 32m, 150.55m, -45m, null, DBNull.Value );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 150.55m );

    //  result = RunStatFunction( max, accTypeDoubleX2, "b", "a", "e", "�", null, DBNull.Value );
    //  Debug.Assert( ( string )result.Value == "�" );

    //  result = RunStatFunction( max, accTypeDoubleX2, 1, 10.6, 32f, 150.55m, -45m, null, DBNull.Value );
    //  Debug.Assert( result.Value is ArgumentException );

    //  result = RunStatFunction( max, accTypeDoubleX2, 1, max, 32, 150, -45, null, DBNull.Value );
    //  Debug.Assert( result.Value is Stats.InvalidValueException );

    //  // **************
    //  // MedianFunction
    //  // **************
    //  MedianFunction median = new MedianFunction();
    //  result = RunStatFunction( median, accTypeInt16, 1, 32, 10, 150, -45 );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == 10 );

    //  result = RunStatFunction( median, accTypeInt16, 1, 32, 10, 150, -45, 11 );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == 10.5 );

    //  result = RunStatFunction( median, accTypeDouble, 1, 32, DBNull.Value, 10, 150, -45, null );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == 10 );

    //  result = RunStatFunction( median, accTypeDecimal, 1, 32, 10, 150, -45, 12 );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 11 );

    //  result = RunStatFunction( median, accTypeDecimal, 1, 32, 10, 150, -45, 10 );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 10 );

    //  result = RunStatFunction( median, accTypeDecimal, 1, 32, 10, median, 150, -45, 12 );
    //  Debug.Assert( result.Value is Stats.InvalidValueException );

    //  result = RunStatFunction( median, accTypeDouble );
    //  Debug.Assert( result.Value == null );

    //  // ***************
    //  // MinimumFunction
    //  // ***************
    //  MinimumFunction min = new MinimumFunction();
    //  result = RunStatFunction( min, accTypeDoubleX2, 1, 10, 32, 150, -45 );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == -45 );

    //  result = RunStatFunction( min, accTypeDoubleX2, 1, 10, 32, 150, -45, null, DBNull.Value );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == -45 );

    //  result = RunStatFunction( min, accTypeDoubleX2, 1f, 10f, 32f, 150.5f, -45.5f, null, DBNull.Value );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == -45.5 );

    //  result = RunStatFunction( min, accTypeDoubleX2, 1m, 10.6m, 32m, 150.55m, -45.55m, null, DBNull.Value );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == -45.55m );

    //  result = RunStatFunction( min, accTypeDoubleX2, "b", "a", "e", "�", null, DBNull.Value );
    //  Debug.Assert( ( string )result.Value == "a" );

    //  result = RunStatFunction( min, accTypeDoubleX2, 1, 10.6, 32f, 150.55m, -45m, null, DBNull.Value );
    //  Debug.Assert( result.Value is ArgumentException );

    //  result = RunStatFunction( min, accTypeDoubleX2, 1, min, 32, 150, -45, null, DBNull.Value );
    //  Debug.Assert( result.Value is Stats.InvalidValueException );

    //  // ************
    //  // ModeFunction
    //  // ************
    //  ModeFunction mode = new ModeFunction();
    //  result = RunStatFunction( mode, accTypeInt16, 1, 10, 32, 150, 1 );
    //  Debug.Assert( Convert.ToInt64( result.Value ) == 1 );

    //  result = RunStatFunction( mode, accTypeInt16, 10, "a", "a", 150.5, 10, "a" );
    //  Debug.Assert( result.Value.ToString() == "a" );

    //  result = RunStatFunction( mode, accTypeInt16, 10, "a", "a", 150.5, 10, "a", 150.5, 150.5m, 150.5m );
    //  Debug.Assert( result.Value.ToString() == "a" );

    //  result = RunStatFunction( mode, accTypeInt16, 10, 150.5, 150.5, "a", "a", 150.5, 10, "a", 150.5, 150.5m );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == 150.5 );

    //  result = RunStatFunction( mode, accTypeInt16, 10, 150.5m, 150.5, "a", "a", 150.5m, 10, "a", 150.5m, 150.5m );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 150.5m );

    //  result = RunStatFunction( mode, accTypeInt16, 1, 10, 32, 150, -45 );
    //  Debug.Assert( result.Value == null );

    //  result = RunStatFunction( mode, accTypeInt16 );
    //  Debug.Assert( result.Value == null );

    //  mode.AllowMultimodal = true;

    //  result = RunStatFunction( mode, accTypeInt16, 10, "a", "a", 150.5, 10, "a", 150.5m, 150.5m, 150.5m );
    //  Debug.Assert( ( result.Value is object[] ) && ( ( ( object[] )result.Value ).Length == 2 ) );

    //  result = RunStatFunction( mode, accTypeInt16, 1, 10, 32, 150, -45 );
    //  Debug.Assert( ( result.Value is object[] ) && ( ( ( object[] )result.Value ).Length == 0 ) );

    //  result = RunStatFunction( mode, accTypeInt16 );
    //  Debug.Assert( ( result.Value is object[] ) && ( ( ( object[] )result.Value ).Length == 0 ) );

    //  // **********************
    //  // RootMeanSquareFunction
    //  // **********************
    //  RootMeanSquareFunction rms = new RootMeanSquareFunction();
    //  result = RunStatFunction( rms, accTypeInt16, 1, 10, 32, 150, -45 );
    //  Debug.Assert( ( int )( Convert.ToDouble( result.Value ) * 100 ) == 7162 );

    //  result = RunStatFunction( rms, accTypeInt16, 1, 10, 32, DBNull.Value, 150, -45, null );
    //  Debug.Assert( ( int )( Convert.ToDouble( result.Value ) * 100 ) == 7162 );

    //  result = RunStatFunction( rms, accTypeDouble, 5.0, 5.0 );
    //  Debug.Assert( Convert.ToDouble( result.Value ) == 5d );

    //  result = RunStatFunction( rms, accTypeDecimal, 5m, 5m );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 5m );

    //  result = RunStatFunction( rms, accTypeDouble );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  result = RunStatFunction( rms, accTypeString, "soidj", "sdoij" );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  // *************************
    //  // StandardDeviationFunction
    //  // *************************
    //  StandardDeviationFunction stdev = new StandardDeviationFunction();
    //  result = RunStatFunction( stdev, accTypeInt16, 1, 10, 32, 150, -45 );
    //  Debug.Assert( ( int )( Convert.ToDouble( result.Value ) * 100 ) == 7291 );

    //  result = RunStatFunction( stdev, accTypeDouble, null, 1d, 10d, 32d, DBNull.Value, 150d, -45.5d );
    //  Debug.Assert( ( int )( Convert.ToDouble( result.Value ) * 100 ) == 7304 );

    //  result = RunStatFunction( stdev, accTypeDecimal, 1m, 10m, 32m, 150m, -45.5m );
    //  Debug.Assert( ( int )( Convert.ToDecimal( result.Value ) * 100 ) == 7304 );

    //  result = RunStatFunction( stdev, accTypeDecimal );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  result = RunStatFunction( stdev, accTypeDecimal, 10 );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  stdev.EntirePopulation = true;

    //  result = RunStatFunction( stdev, accTypeInt16, 1, 10, 32, 150, -45 );
    //  Debug.Assert( ( int )( Convert.ToDouble( result.Value ) * 100 ) == 6522 );

    //  result = RunStatFunction( stdev, accTypeDecimal, 10 );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 0d ) );

    //  result = RunStatFunction( stdev, accTypeDecimal );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  // ***********
    //  // SumFunction
    //  // ***********
    //  SumFunction sum = new SumFunction();
    //  result = RunStatFunction( sum, accTypeInt32, 1, 10, -3, null, 32.38, DBNull.Value );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 40d ) );

    //  result = RunStatFunction( sum, accTypeDouble, 1, 10, -3, null, 32.38, DBNull.Value );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 40.38 ) );

    //  result = RunStatFunction( sum, accTypeDecimal, 1, 10, -3, null, 32.38, DBNull.Value );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 40.38m );

    //  result = RunStatFunction( sum, accTypeInt16, Int64.MaxValue, 1 );
    //  Debug.Assert( result.Value is OverflowException );

    //  // ****************
    //  // VarianceFunction
    //  // ****************
    //  VarianceFunction var = new VarianceFunction();
    //  result = RunStatFunction( var, accTypeInt32, 1, 10, null, 5, -5, DBNull.Value );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 40.25d ) );

    //  result = RunStatFunction( var, accTypeDouble, 1, 10, null, 5, -5, DBNull.Value );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 40.25d ) );

    //  result = RunStatFunction( var, accTypeDecimal, 1, 10, null, 5, -5, DBNull.Value );
    //  Debug.Assert( Convert.ToDecimal( result.Value ) == 40.25m );

    //  result = RunStatFunction( var, accTypeInt32, 1, 10 );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 40.5 ) );

    //  result = RunStatFunction( var, accTypeInt32 );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  result = RunStatFunction( var, accTypeInt32, 10 );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  var.EntirePopulation = true;

    //  result = RunStatFunction( var, accTypeInt32, 1, 10, null, 5, -5, DBNull.Value );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 30.1875 ) );

    //  result = RunStatFunction( var, accTypeInt32 );
    //  Debug.Assert( result.Value is DivideByZeroException );

    //  result = RunStatFunction( var, accTypeInt32, 10 );
    //  Debug.Assert( DoubleUtil.AreClose( Convert.ToDouble( result.Value ), 0d ) );

    //  Debug.WriteLine( "STATFUNCTIONS SELF-TESTS HAVE ENDED." );
    //}

    //private static StatResult RunStatFunction( StatFunction statFunction, Type[] dataTypes, params object[] values )
    //{
    //  return StatFunction.RunStatFunction( statFunction, dataTypes, values, null );
    //}

    //private static StatResult RunStatFunction(
    //  StatFunction statFunction,
    //  Type[] dataTypes,
    //  object[] valuesX,
    //  object[] valuesY )
    //{
    //  statFunction.InitializeAccumulationTypes( dataTypes );
    //  statFunction.StartCalculation();
    //  statFunction.Reset();

    //  try
    //  {
    //    if( statFunction.PrerequisiteFunctions != null && statFunction.PrerequisiteFunctions.Length > 0 )
    //    {
    //      StatResult[] preValues = new StatResult[ statFunction.PrerequisiteFunctions.Length ];

    //      for( int i = 0; i < statFunction.PrerequisiteFunctions.Length; i++ )
    //      {
    //        preValues[ i ] = StatFunction.RunStatFunction( statFunction.PrerequisiteFunctions[ i ], dataTypes, valuesX, valuesY );

    //        if( preValues[ i ].Value is Exception )
    //          throw ( Exception )preValues[ i ].Value;
    //      }

    //      statFunction.InitializePrerequisites( preValues );
    //    }

    //    if( statFunction.RequiresAccumulation )
    //    {
    //      for( int i = 0; i < valuesX.Length; i++ )
    //      {
    //        if( valuesY == null )
    //        {
    //          statFunction.Accumulate( new object[] { valuesX[ i ] } );
    //        }
    //        else
    //        {
    //          statFunction.Accumulate( new object[] { valuesX[ i ], valuesY[ i ] } );
    //        }
    //      }
    //    }

    //    statFunction.EndCalculation();
    //    return statFunction.GetResult();
    //  }
    //  catch( Exception ex )
    //  {
    //    return new StatResult( ex );
    //  }
    //}
#endif

    private bool m_initialized; // = false
    private bool m_inCalculation;

    private List<string> m_sourcePropertyNames = new List<string>( 0 );
    private IList<string> m_readOnlySourcePropertyNames = null;
  }
}
